package org.angelica.common.filter;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.angelica.common.AppConstant;
import org.angelica.core.common.Constant;
import org.angelica.core.response.ResponseResult;
import org.angelica.enums.RedisKeyEnum;
import org.angelica.enums.ResponseErrorEnum;
import org.angelica.utils.common.ToolUtil;
import org.angelica.utils.redis.RedisUtil;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 请求签名验证过滤器
 * @author aiken
 */
public class SignaureFilter implements Filter {

	/**
	 * 不进行签名的接口请求
	 */
	private List<Pattern> excludeNoSignUris = Lists.newArrayList();
	/**
	 * 只进行简单签名的请求
	 */
	private List<Pattern> excludeSimpleSignUris = Lists.newArrayList();
	
	@Override
	public void init(FilterConfig filterConfig) {

		// 不进行签名的接口请求
		excludeNoSignUris.addAll(FilterHelper.excludeNoSignApi());
		// 只进行简单签名的请求：仅随机数和时间戳参与签名；因为部分接口比较复杂比如有表情字符，导致前端签名和后端签名不一致
		excludeSimpleSignUris.addAll(FilterHelper.excludeSimpleSignApi());
	}
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest hsRequest = (HttpServletRequest)request;
		HttpServletResponse hsResponse = (HttpServletResponse)response;
		
		/*
		    1、如果不是API接口请求，则放行；
            2、部分不需要进行签名的接口，则放行
        */
		if(!FilterHelper.isApiRequest(hsRequest.getRequestURI())
				|| isExcludeNoSignUri(hsRequest.getRequestURI())) {
			chain.doFilter(request, response);
			return;
		}

		String sign = hsRequest.getHeader("sign");
		String timestampStr = hsRequest.getHeader("timestamp");
		String nonce = hsRequest.getHeader("nonce");
		
		//判断请求头是否合法
		if(StringUtils.isBlank(sign) || StringUtils.isBlank(timestampStr) || StringUtils.isBlank(nonce)) {
            ToolUtil.responseError(hsResponse, ResponseResult.error(ResponseErrorEnum.MISSING_HEADER));
			return;
		}

		//判断时间戳是否合法
		long expiredTime = (System.currentTimeMillis() - Long.valueOf(timestampStr)) / 1000;
		if(expiredTime > Constant.REQUEST_EXPIRED_TIME) {
			ToolUtil.responseError(hsResponse, ResponseResult.error(ResponseErrorEnum.REQUEST_TIME_OUT));
			return;
		}

		//判断是否为重放请求
        String nonceKey = RedisKeyEnum.NONCE.getPrefix() + nonce;
        if(RedisUtil.hasKey(nonceKey)){
			ToolUtil.responseError(hsResponse, ResponseResult.error(ResponseErrorEnum.REQUEST_DUPLICATE));
			return;
		}else{
            RedisUtil.set(nonceKey,"1",RedisKeyEnum.NONCE.getExpiredTime());
        }

		//获取所有请求参数 并判断请求参数是否合法
		Map<String,String> paramMap = Maps.newHashMap();
		if(!isExcludeSimpleSignUri(hsRequest.getRequestURI())){
            Enumeration<String> enu = hsRequest.getParameterNames();
            while(enu.hasMoreElements()) {
                String paramName = enu.nextElement();
                paramMap.put(paramName, hsRequest.getParameter(paramName));
            }
        }
		paramMap.put("timestamp", timestampStr);
		paramMap.put("nonce", nonce);

		if(!sign.equals(ToolUtil.sign(AppConstant.APP_ID, AppConstant.APP_SECRET, paramMap))) {
			ToolUtil.responseError(hsResponse, ResponseResult.error(ResponseErrorEnum.SIGN_ERROR));
			return;
		}

		chain.doFilter(request, response);
	}

	@Override
	public void destroy() {
		
	}

	/**
	 * 判断是否是排除过滤的URI
	 * @param requestUri
	 * @return
	 */
	private boolean isExcludeNoSignUri(String requestUri) {
		for (Pattern pattern : excludeNoSignUris) {
			Matcher matcher = pattern.matcher(requestUri);
			if (matcher.matches()) {
				return true;
			}
		}
		return false;
	}

    /**
     * 判断是否排除参数签名的url
     * @param requestUri
     * @return
     */
	private boolean isExcludeSimpleSignUri(String requestUri){
        for (Pattern pattern : excludeSimpleSignUris) {
            Matcher matcher = pattern.matcher(requestUri);
            if (matcher.matches()) {
                return true;
            }
        }
        return false;
    }

}
